/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package simframe.simulation;

import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Steve Noble
 *
 * A simulation is a set of SimThreads that are synchronized by the Synchronizer object
 * executed at given time
 *
 * There is always the notion of a curret time of the current action being performed
 */
public class Simulation implements Runnable {
      private static enum State {
        Init,
        Paused,
        Running
    }

	// controlling the simulation time
    // this represents global time control
     class SimulationController implements Synchronizer.TimeController {

        private long globalTime;

        synchronized boolean checkTime() throws InterruptedException {

            while (curTime > globalTime) {
                wait();
            }

            return true;
        }

        public synchronized void updateGlobalClockTime(long t) {
            globalTime = t;
            this.notifyAll();
        }

         public long getNextTime() {
             return Time.next(curTime, period);
         }

        synchronized void reset() {
            globalTime = 0;

            notifyAll();
        }
    }

    final Synchronizer synchro;
    private final SimulationController simControl;
    
	private long ticRate = 0;
    private Long curTime = new Long(0);
    private State simState = State.Init;
    private Time.Period period = Time.Period.Daily;

    private List<SimThread> sims = new ArrayList<SimThread>();

    private Map<SimViewer, Long> viewers = new HashMap<SimViewer, Long>();

    private List<MetricDisplay> graphers = new ArrayList<MetricDisplay>();
    private List<Metric> metrics = new ArrayList<Metric>();
    private List<SimParameter> parms = new ArrayList<SimParameter>();

    private long endTime = Long.MAX_VALUE;

      public Simulation() {
        synchro = new Synchronizer();
        simControl = new SimulationController();
    }

    public  synchronized void addViewer(SimViewer viewer, long daysPerView) {
        viewers.put(viewer, daysPerView);
    }

     public  synchronized void removeViewer(SimViewer viewer) {
        viewers.remove(viewer);
    }
     
      public  synchronized void addMetricDisplay(MetricDisplay metricDisplay) {
          graphers.add(metricDisplay);
      }

     public  synchronized void removeMetricDisplay(MetricDisplay metricDisplay) {
         graphers.remove(metricDisplay);
      }

     public synchronized  List<Metric> getMetrics() {
         return metrics;
     }

      public void addMetric(Metric m) {
          metrics.add(m);
      }

      public void addParameter(SimParameter parm) {
          parms.add(parm);
      }

      public List<SimParameter> getParameters() {
          return parms;
      }

      /*
       * Adds a new thread to the simulation
       */
    public SimThread newThread() {
        SimThread rval = new SimThread(this);
        sims.add(rval);
        
        return rval;
    }

    public synchronized long getTime() {
        return curTime;
    }

	synchronized void start() {
		simState  = State.Running;
	}

	public synchronized void stop() {
		simState = State.Paused;
	}

	public synchronized  boolean stopped() {
		 return simState == State.Paused || simState == State.Init;
	}

    public synchronized void reset() {
        stop();

       simState = State.Init;

       simControl.reset();

        curTime = new Long(0);

        synchro.updateTime(simControl);

        for(SimThread sim : sims) {
            sim.reset();
        }

         for(SimViewer viewer : viewers.keySet()) {
            viewer.reset();
        }
    }

    // called after a sim step
	private synchronized void update(long elapsedTime) {

        long numDays = elapsedTime/Time.Day;

        for(SimViewer viewer : viewers.keySet()) {
            long dayToUpdate = viewers.get(viewer);
            if (numDays % dayToUpdate == 0) {
                viewer.update(this.curTime);
            }
        }
//
//        if (graphers.size() > 0) {
//            for (Metric metric : metrics) {
//                metric.update();
//            }
//        }

        for(MetricDisplay metricDisplay : graphers) {
            metricDisplay.update(this.curTime);
        }
	}

	// if numSteps is zero
	// sim does never waits
	public synchronized void DaysPerSecond(long numDays) {
		if ( numDays > 0) {
			ticRate = Math.round(1000.0/numDays);
		}
		else {
			ticRate = 0;
		}
	}

    public void setEndTime(long t) {
        endTime = t;
    }

	public void run() {

        start();
        
        for(SimThread sim : sims) {
            sim.start();
        }

    	long startTic = System.currentTimeMillis();
       
        Long startTime = curTime;
        while (!stopped() && curTime <= endTime) {
            try {
                // need to check if we are running faster then the simulation
                // checkTime(curTime.getTics()))
                //
                simControl.checkTime();
            } catch (InterruptedException ex) {
                Logger.getLogger(Simulation.class.getName()).log(Level.SEVERE, null, ex);
            }

            this.update(curTime - startTime);

            System.out.print("Simulation Time = " + Time.toString(curTime) + "\n");

            if (ticRate > 0) {
                long curTic = System.currentTimeMillis();


                // ticRate indicates how many tics at min we want between steps
                long sleepTime = ticRate * (this.curTime - startTime)/Time.Day - (curTic - startTic);
                if (sleepTime > 0) {
                    try {
                        Thread.sleep(sleepTime);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }

            curTime = Time.next(curTime, period);
            synchro.updateTime(simControl);
        }
	}
}
